package model;

import java.io.*;
import java.awt.Point;

public class TockBoard implements Serializable, TockConstants {
	public static final long serialVersionUID = 0; // What the hell is this?
	
	private Location[][] locations;
	
	// Returns the location given the pieces colour and number (0-3)
	public Location getLocation(int col, int num) { return locations[col][num]; }
	
	// Sets the location of a given marble (colour and number)
	public void setLocation(int col, int num, Location loc) { locations[col][num] = loc; }
	
	// Constructor
	public TockBoard() {
		locations = new Location[4][4];
		
		//--- Sets all the default locations to the start positions
		for (int i=0; i<4; i++) {
			for (int j=0; j<4; j++) {
				locations[i][j] = new Location(LOC_TYPE_START, i, j);
			}
		}
		
	}
	
	/**
	 * Returns the come out location of a given marble.
	 * @param col the colour of the marble in question
	 * @param loc which of the 4 marbles it is (0-3)
	 * @return
	 */
	public int isHome(int col, int loc) {
		if (locations[col][loc].type != LOC_TYPE_START) return -1;
		else return COME_OUT_SPOTS[col];
	}
	
	/**
	 * The function that moves a piece. Assumes that the move is valid
	 * @param source Location of where the marble is
	 * @param dest Location of where the marble wants to go
	 * @param player Id of the player (0-3)
	 * @param move_type The type of the move to conduct
	 */
	private void movePiece(Location source, Location dest, int player, int move_type) {
		//----- Get the marble id's if there are any at the locations.
		int[] source_marble = pieceAt(source);
		int[] dest_marble = pieceAt(dest);
		
		switch (move_type) {
			case (MOVE_TYPE_NORMAL):
				locations[source_marble[0]][source_marble[1]] = dest;
				return;
			case (MOVE_TYPE_TOCK):
				handleTock(source, dest);
				return;
			case (MOVE_TYPE_SELF_TOCK):
				handleTock(source, dest);
				return;
			case (MOVE_TYPE_SWITCH):
				// Check and make sure we didn't just tock ourselves
				if (inSafeSpot(dest.loc, dest_marble[0])) {
					locations[source_marble[0]][source_marble[1]] = new Location(LOC_TYPE_START, source_marble[0], source_marble[1]);
				} else {
					locations[source_marble[0]][source_marble[1]] = dest;
					locations[dest_marble[0]][dest_marble[1]] = source;
				}
				return;
			default:
				return;
		}
	}
	
	/**
	 * Method to move a piece. Assumes that this is a valid move.
	 * @param source Location of where the marble is
	 * @param dest Location of where the marble wants to go
	 * @param player Id of the player (0-3)
	 * @param ten_switch If this is a 10 switch or not
	 */
	public void movePiece(Location source, Location dest, int player, boolean ten_switch) {
		//----- Get the marble id's if there are any at the locations.
		int[] source_marble = pieceAt(source);
		int[] dest_marble = pieceAt(dest);
		
		//----- Check if it's a normal move
		if (dest_marble[0] == -1) {
			movePiece(source, dest, player, MOVE_TYPE_NORMAL);
		} else {
			//--- Check if it's a switch
			if (ten_switch) {
				movePiece(source, dest, player, MOVE_TYPE_SWITCH);
			}
			//--- Must be a tock
			else {
				//--- Check if we've self tocked
				if (source_marble[0] == dest_marble[0])
					movePiece(source, dest, player, MOVE_TYPE_SELF_TOCK);
				else {
					//-- Must be a normal tock
					movePiece(source, dest, player, MOVE_TYPE_TOCK);
				}
			}
		}
	}
	
	
	/**
	 * See if a location is occupied
	 * @param loc the location object
	 * @return The colour and marble number of the marble in the given location. [-1, -1] if no marble is there
	 */
	public int[] pieceAt(Location loc) {
		int[] toReturn = new int[2];
		toReturn[0] = -1;
		toReturn[1] = -1;
		
		for (int i=0; i<4; i++) {
			for (int j=0; j<4; j++) {
				if (locations[i][j].isEqual(loc)) {
					toReturn[0] = i;
					toReturn[1] = j;
				}
			}
		}
		
		return toReturn;
	}
	
	/**
	 * Handles a tock situation (self or inflicting). This should only be called in a tocking
	 *  situation. i.e. there are actually marbles at the given locations.
	 * @param source The source marble location
	 * @param target The target marble location
	 */
	public void handleTock(Location source, Location target) {
		//--- Sanity check
		if (source.isEqual(target)) return;
		
		int s_Col=0, t_Col=0, s_Num=0, t_Num=0; // The (s)ource and (t)arget (Col)our and marble (Num)ber
		
		//--- Get a hold of the colour and number of the marbles
		for (int i=0; i<4; i++) {
			for (int j=0; j<4; j++) {
				if (source.isEqual(locations[i][j])) {
					s_Col = i;
					s_Num = j;
				}
				if (target.isEqual(locations[i][j])) {
					t_Col = i;
					t_Num = j;
				}
			}
		}
		
		//--- Check if the marble is in a safe spot
		if (inSafeSpot(target.loc, t_Col)) {
			// The source is tocked
			locations[s_Col][s_Num] = new Location(LOC_TYPE_START, s_Col, s_Num);
		} else {
			// The target is tocked
			locations[s_Col][s_Num] = locations[t_Col][t_Num];
			locations[t_Col][t_Num] = new Location(LOC_TYPE_START, t_Col, t_Num);
		}
	}
	
	/**
	 * Function to check if a given marble in normal spots is in a safe spot.
	 *   It checks the 8-safe spots as well as the home spot of the given player.
	 * @param target The spot the marble is currently on
	 * @param colour The colour of the marble
	 * @return
	 */
	public boolean inSafeSpot (int target, int colour) {
		//--- Check the 8-safe spots
		for (int i=0; i<SAFE_SPOTS.length; i++) {
			if (SAFE_SPOTS[i] == target) return true;
		}
		
		//--- Check the home spot
		if (COME_OUT_SPOTS[colour] == target) return true;
		
		return false;
	}
	
	
	
	/**
	 * Checks if any marble at the given location can move given the card amount.
	 * Note: This function assumes that the location type is normal
	 * @param loc The location object in question
	 * @param card Value of the card we want to move.
	 * @return
	 */
	public boolean canPass(Location _loc, int card) {
		if (_loc.type != LOC_TYPE_NORMAL) return false;
		
		int loc = _loc.loc;
		
		//----- Switch it up to correctly check for the 4-back card
		if (card == -4) {
			loc -= 5;
			card = 4;
			if (loc < 0) loc += 72;
		} 
		
		for (int i = 1; i <= card; i++) {
			int temp = (loc + i) % 72;
			
			//--- For each of the safe spots we check if a home piece is there.
			if (0 == (temp % 18)) {
				if (rightPiece((temp/18), new Location(LOC_TYPE_NORMAL, temp)))
					return false;
			}
		}
		return true;
	}
	
	/**
	 * Method to see if a given player has a marble at the given location
	 * @param player id of the player (0-3)
	 * @param loc location to check against.
	 * @return
	 */
	public boolean rightPiece(int player, Location loc) {
		for (int i=0; i<4; i++) {
			if (loc.isEqual(locations[player][i])) return true;
		}
		return false;
	}
	
	/**
	 * Method to see if a player has any pieces in their start squares
	 * @param player The player id (0-3)
	 * @return if it has a piece or not...
	 */
	public boolean hasComeOutPiece(int player) {
		for (int i=0; i<4; i++) {
			if (locations[player][i].type == LOC_TYPE_START) return true;
		}
		return false;
	}
	
	/**
	 * Helper method to lookup the Point coordinate of a given Location
	 * @param loc Location we want to locate
	 * @return The Point coordinates for that location
	 */
	public static Point findLocation(Location loc) {
		switch (loc.type) {
			case (LOC_TYPE_NORMAL):
				return NORMAL_SPOTS[loc.loc];
			case (LOC_TYPE_START):
				return START_SPOTS[loc.color][loc.loc];
			case (LOC_TYPE_HOME):
				return HOME_SPOTS[loc.color][loc.loc];
			default:
				return null;
		}
	}
	
	/**
	 * Helper function to calculate the move distance between two locations
	 * @param src The source location of the marble
	 * @param dst The destination location of the marble
	 * @return The distance between the two locations.
	 */
	public static int calcDistance(Location src, Location dst) {
		if (LOC_TYPE_NORMAL == src.type) {
			if (LOC_TYPE_NORMAL == dst.type) {
				//--- Check if it's wrapped around the board
				if (dst.loc < src.loc) {
					return (dst.loc + 72) - src.loc;
				}
				return dst.loc - src.loc;				
			} else if (LOC_TYPE_HOME == dst.type) {
				//--- Find the spot just before the home
				int doorStep = (18 * dst.color) - 1;
				doorStep = (doorStep < 0) ? doorStep + 72 : doorStep;
				
				//--- Should return the distance to the doorStep + moves to get into the home
				return calcDistance(src, new Location(LOC_TYPE_NORMAL, doorStep)) + (dst.loc + 1);
			}
		} else if (LOC_TYPE_HOME == src.type) {
			if (LOC_TYPE_HOME == dst.type) {
				if (src.color == dst.color)
					return dst.loc - src.loc;
			}
		}
		
		//----- Default is 0
		return 0;
	}
	
	
	/**
	 * Checks for the boards being equal. This will return true if for each of the
	 *  marbles of a certain colour have a location matching the marbles colour in
	 *  the other board.
	 * @param other The other board we want to compare to this
	 * @return
	 */
	public boolean isEqual(TockBoard other) {
		int target = locations.length * locations[0].length;
		int total = 0;
		for (int sourceCol=0; sourceCol<locations.length; sourceCol++) {
			for (int sourceMarb=0; sourceMarb<locations[0].length; sourceMarb++) {
				for (int destMarb=0; destMarb<locations[0].length; destMarb++) {
					if (locations[sourceCol][sourceMarb].isEqual(other.getLocation(sourceCol, destMarb)))
						total++;
				}				
			}
		}
		if (total < target) return false;
		else return true;
	}
}



/********************* GRAVEYARD *********************/
/**
 * This function checks to see if a marble has the option of going home.
 * @param loc The current location of the marble
 * @param card The value of the card (number of spaces it can move)
 * @param player The player number from 0-3
 * @return
 *
public boolean canGoHome(Location loc, int card, int player) {
	//----- Sanity check on 4's, or bad player id's
	if (card == -4) return false;
	if (player == -1) return false;
	
	//----- If it's in the start, it can't go home
	if (loc.type == LOC_TYPE_START) return false;
	
	//----- If it's home, check to see if it can go further
	else if (loc.type == LOC_TYPE_HOME) {
		//--- Check if the card is too big
		if (card > 3) return false;
		
		//--- Check it's path
		for (int i=0; i<card; i++) {
			if ((pieceAt(new Location(LOC_TYPE_HOME, player, i)))[0] != -1) return false;
		}
		return true;
	}
	
	//----- It must be in the player area.
	else {
	
		
		
		if ((player == 0) && ((dest >= 0) && (dest <= 3))) {
			for (int i = 0; i < dest; i++) {
				if ((pieceAt(new Location(LOC_TYPE_HOME, player, i)))[0] != -1) return false;
			}
			return true;
		}
		
		else if ((player == 1) && ((dest >= 18) && (dest <= 21))) {
			for (int i = 0; i < dest-18; i++) {
				if ((pieceAt(new Location(LOC_TYPE_HOME, player, i)))[0] != -1) return false;
			}
			return true;
		}
		
		else if ((player == 2) && ((dest >= 36) && (dest <= 39))) {
			for (int i = 0; i < dest-36; i++) {
				if ((pieceAt(new Location(LOC_TYPE_HOME, player, i)))[0] != -1) return false;
			}
			return true;
		}
		
		else if ((player == 3) && ((dest >= 54) && (dest <= 57))) {
			for (int i = 0; i < dest-54; i++) {
				if ((pieceAt(new Location(LOC_TYPE_HOME, player, i)))[0] != -1) return false;
			}
			return true;
		}
		
		else {
			System.out.println("Bad Player: " + player);
			System.exit(0);
			return false;
		}
	}
	
}

*/